package com.glodon.ggzy.core.dao;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.hibernate.Criteria;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

import com.glodon.ggzy.core.model.Page;
import com.glodon.ggzy.core.utils.Constants;
import com.glodon.ggzy.core.utils.QuerySupport;
import com.glodon.ggzy.core.utils.ReflectionUtils;

/**
 * 封装Hibernate原生API的DAO泛型基类.
 * 
 * 直接引用springside-3.3中的SimpleHibernateDao基类.
 * 可在Service层直接使用,也可以扩展泛型DAO子类使用.
 * 参考Spring2.5自带的Petlinc例子,取消了HibernateTemplate,直接使用Hibernate原生API.
 * 
 * @param <T> DAO操作的对象类型
 * @param <PK> 主键类型
 * 
 * @author calvin
 * @author yangjf 
 */
@SuppressWarnings("unchecked")
public class SimpleHibernateDao<T, PK extends Serializable> implements ISimpleHibernateDao<T, PK> {

	protected Logger logger = LoggerFactory.getLogger(getClass());

	protected SessionFactory sessionFactory;

	protected Class<T> entityClass;

	MyHibernateTemplate hibernateTemplate;

	protected Boolean hasWhere = false;

	/**
	 * 用于Dao层子类使用的构造函数.
	 * 通过子类的泛型定义取得对象类型Class.
	 * eg.
	 * public class UserDao extends SimpleHibernateDao<User, Long>
	 */
	public SimpleHibernateDao() {
		this.entityClass = ReflectionUtils.getSuperClassGenricType(getClass());
	}

	/**
	 * 用于用于省略Dao层, 在Service层直接使用通用SimpleHibernateDao的构造函数.
	 * 在构造函数中定义对象类型Class.
	 * eg.
	 * SimpleHibernateDao<User, Long> userDao = new SimpleHibernateDao<User, Long>(sessionFactory, User.class);
	 */
	public SimpleHibernateDao(final Class<T> entityClass) {
		this.entityClass = entityClass;
	}

	/**
	 * 保存新增或修改的对象.
	 */
	@Override
	public void save(final T entity) {
		Assert.notNull(entity, "entity不能为空");
		getSession().saveOrUpdate(entity);
		getSession().flush();
		logger.debug("save entity: {}", entity);
	}

	/**
	 * 删除对象.
	 * 
	 * @param entity 对象必须是session中的对象或含id属性的transient对象.
	 */
	@Override
	public void delete(final T entity) {
		Assert.notNull(entity, "entity不能为空");
		getSession().delete(entity);
		getSession().flush();
		logger.debug("delete entity: {}", entity);
	}

	/**
	 * 按id删除对象.
	 */
	public void delete(final PK id) {
		Assert.notNull(id, "id不能为空");
		delete(get(id));
		logger.debug("delete entity {},id is {}", entityClass.getSimpleName(), id);
	}

	@Override
	public void deleteById(final PK id) {
		delete(id);
	}

	/**
	 * 按id获取对象.
	 */
	@Override
	public T get(final PK id) {
		Assert.notNull(id, "id不能为空");
		return (T) getSession().load(entityClass, id);
	}

	/**
	 * 按id列表获取对象列表.
	 */
	@Override
	public List<T> get(final Collection<PK> ids) {
		return find(Restrictions.in(getIdName(), ids));
	}

	/**
	 *	获取全部对象.
	 */
	@Override
	public List<T> getAll() {
		return find();
	}

	/**
	 *	获取全部对象, 支持按属性行序.
	 */
	@Override
	public List<T> getAll(String orderByProperty, boolean isAsc) {
		Criteria c = createCriteria();
		if (isAsc) {
			c.addOrder(Order.asc(orderByProperty));
		} else {
			c.addOrder(Order.desc(orderByProperty));
		}
		return c.list();
	}

	/**
	 * 按属性查找对象列表, 匹配方式为相等.
	 */
	@Override
	public List<T> findBy(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return find(criterion);
	}

	/**
	 * 按属性查找唯一对象, 匹配方式为相等.
	 */
	public T findUniqueBy(final String propertyName, final Object value) {
		Assert.hasText(propertyName, "propertyName不能为空");
		Criterion criterion = Restrictions.eq(propertyName, value);
		return (T) createCriteria(criterion).uniqueResult();
	}

	/**
	 * 按HQL查询对象列表.
	 * 
	 * @param values 数量可变的参数,按顺序绑定.
	 */
	@Override
	public <X> List<X> find(final String hql, final Object... values) {
		return createQuery(hql, values).list();
	}

	/**
	 * 按HQL查询对象列表.
	 * 
	 * @param values 命名参数,按名称绑定.
	 */
	@Override
	public <X> List<X> find(final String hql, final Map<String, ?> values) {
		return createQuery(hql, values).list();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values 数量可变的参数,按顺序绑定.
	 */
	@Override
	public <X> X findUnique(final String hql, final Object... values) {
		return (X) createQuery(hql, values).uniqueResult();
	}

	/**
	 * 按HQL查询唯一对象.
	 * 
	 * @param values 命名参数,按名称绑定.
	 */
	@Override
	public <X> X findUnique(final String hql, final Map<String, ?> values) {
		return (X) createQuery(hql, values).uniqueResult();
	}

	/**
	 * 执行HQL进行批量修改/删除操作.
	 * 
	 * @param values 数量可变的参数,按顺序绑定.
	 * @return 更新记录数.
	 */
	@Override
	public int batchExecute(final String hql, final Object... values) {
		return createQuery(hql, values).executeUpdate();
	}

	/**
	 * 执行HQL进行批量修改/删除操作.
	 * 
	 * @param values 命名参数,按名称绑定.
	 * @return 更新记录数.
	 */
	@Override
	public int batchExecute(final String hql, final Map<String, ?> values) {
		return createQuery(hql, values).executeUpdate();
	}

	/**
	 * 根据查询HQL与参数列表创建Query对象.
	 * 与find()函数可进行更加灵活的操作.
	 * 
	 * @param values 数量可变的参数,按顺序绑定.
	 */
	@Override
	public Query createQuery(final String queryString, final Object... values) {
		Assert.hasText(queryString, "queryString不能为空");
		Query query = getSession().createQuery(queryString);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {
				query.setParameter(i, values[i]);
			}
		}
		return query;
	}

	/**
	 * 根据查询HQL与参数列表创建Query对象.
	 * 与find()函数可进行更加灵活的操作.
	 * 
	 * @param values 命名参数,按名称绑定.
	 */
	@Override
	public Query createQuery(final String queryString, final Map<String, ?> values) {
		Assert.hasText(queryString, "queryString不能为空");
		Query query = getSession().createQuery(queryString);
		if (values != null) {
			query.setProperties(values);
		}
		return query;
	}

	/**
	 * 按Criteria查询对象列表.
	 * 
	 * @param criterions 数量可变的Criterion.
	 */
	@Override
	public List<T> find(final Criterion... criterions) {
		return createCriteria(criterions).list();
	}

	/**
	 * 按Criteria查询唯一对象.
	 * 
	 * @param criterions 数量可变的Criterion.
	 */
	@Override
	public T findUnique(final Criterion... criterions) {
		return (T) createCriteria(criterions).uniqueResult();
	}

	/**
	 * 根据Criterion条件创建Criteria.
	 * 与find()函数可进行更加灵活的操作.
	 * 
	 * @param criterions 数量可变的Criterion.
	 */
	@Override
	public Criteria createCriteria(final Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(entityClass);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		return criteria;
	}

	/**
	 * 初始化对象.
	 * 使用load()方法得到的仅是对象Proxy, 在传到View层前需要进行初始化.
	 * 如果传入entity, 则只初始化entity的直接属性,但不会初始化延迟加载的关联集合和属性.
	 * 如需初始化关联属性,需执行:
	 * Hibernate.initialize(user.getRoles())，初始化User的直接属性和关联集合.
	 * Hibernate.initialize(user.getDescription())，初始化User的直接属性和延迟加载的Description属性.
	 */
	@Override
	public void initProxyObject(Object proxy) {
		Hibernate.initialize(proxy);
	}

	/**
	 * Flush当前Session.
	 */
	@Override
	public void flush() {
		getSession().flush();
	}

	/**
	 * 为Query添加distinct transformer.
	 * 预加载关联对象的HQL会引起主对象重复, 需要进行distinct处理.
	 */
	@Override
	public Query distinct(Query query) {
		query.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
		return query;
	}

	/**
	 * 为Criteria添加distinct transformer.
	 * 预加载关联对象的HQL会引起主对象重复, 需要进行distinct处理.
	 */
	@Override
	public Criteria distinct(Criteria criteria) {
		criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
		return criteria;
	}

	/**
	 * 取得对象的主键名.
	 */
	@Override
	public String getIdName() {
		ClassMetadata meta = getSessionFactory().getClassMetadata(entityClass);
		return meta.getIdentifierPropertyName();
	}

	/**
	 * 判断对象的属性值在数据库内是否唯一.
	 * 
	 * 在修改对象的情景下,如果属性新修改的值(value)等于属性原来的值(orgValue)则不作比较.
	 */
	@Override
	public boolean isPropertyUnique(final String propertyName, final Object newValue, final Object oldValue) {
		if (newValue == null || newValue.equals(oldValue)) {
			return true;
		}
		Object object = findUniqueBy(propertyName, newValue);
		return (object == null);
	}

	/**
	 * 按DetachedCriteria获取所有实体集合  
	 *  
	 * @param criteria 数量可变的Criterion
	 * @return 集合
	 */
	@Override
	public List<T> findByDetachedCriteria(DetachedCriteria detachedCriteria) {
		Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());
		return criteria.list();
	}

	/**
	 * 按DetachedCriteria获取所有实体集合  :前n条
	 *  
	 * @param criteria 数量可变的Criterion
	 * @return 集合
	 */
	@Override
	public List<T> findByDetachedCriteriaByTop(DetachedCriteria detachedCriteria) {
		Criteria criteria = detachedCriteria.getExecutableCriteria(getSession());
		criteria.setFirstResult(0);
		criteria.setMaxResults(Constants.TOP_N);
		return criteria.list();
	}

	public Session getSession() {
		return getSessionFactory().getCurrentSession();
	}

	/*********************************************分页****************************************************/

	/**
	 * 查询一条数据或者字表的分页
	 * @param id
	 * @return
	 */
	public Page pageById(Serializable id) {
		Page pg = new Page();
		List<T> rs = queryById(id);
		int totalCount = rs.size();
		pg.setTotalCount(totalCount);
		pg.setResult(rs);
		pg.setCurrentPage(1);
		pg.setPageSize(Constants.DEFAULT_PAGE_SIZE);
		pg.setPageCount(Page.getPageCount(totalCount, Constants.DEFAULT_PAGE_SIZE));
		return pg;
	}

	/**
	 * 查询hql的分页
	 * @param hql
	 * @param values
	 * @param countHql
	 * @param pageNumber
	 * @param pageSize
	 * @return
	 */
	@Override
	public Page pageQuery(String hql, Object[] values, String countHql, int pageNumber, int pageSize) {
		Assert.notNull(countHql, "countHql can not be null.");
		long totalCount = querySize(countHql, values);
		return pageQuery(totalCount, hql, values, pageNumber, pageSize);
	}
	
	/**
	 * 参数为map
	 * @param hql
	 * @param values
	 * @param countHql
	 * @param pageNumber
	 * @param pageSize
	 * @return
	 */
	public Page pageQuery(String hql, Map<String, Object> values, String countHql,	int pageNumber, int pageSize)
	{
		Assert.notNull(countHql, "countHql can not be null.");
		long totalCount = querySize(countHql, values);
		return pageQuery(totalCount, hql, values, pageNumber, pageSize);
	}
	
	protected Page pageQuery(Long totalCount, String hql, Map<String, Object> values,
			int pageNumber, int pageSize)
	{
		Page page = new Page();
		if (totalCount == null) totalCount = querySize(hql, values);
		long count = Page.getPageCount(totalCount, pageSize);
		pageNumber = (int) (pageNumber > count ? count : pageNumber);
		List<?> result = pageList(hql, values, pageNumber, pageSize);
		page.setTotalCount(totalCount);
		page.setResult(result);
		page.setCurrentPage(pageNumber);
		page.setPageSize(pageSize);
		page.setPageCount(count);
		return page;
	}
	
	protected List<?> pageList(final String hql, final Map<String, Object> values, int pageNumber,
			final int pageSize)
	{
		return pageList(hql, values, null, pageNumber, pageSize);
	}
	
	
	protected List<?> pageList(final String hql, final Map<String, Object> values,
			final Boolean enableCache, int pageNumber, final int pageSize)
	{
		return getHibernateTemplate().pageList(hql, values, enableCache, pageNumber, pageSize);
	}

	
	public long querySize(String hql, Map<String, Object> values)
	{
		if (hql == null) return 0;
		List<?> list = queryList(hql, values);
		if (list.size() == 1)
		{
			Object obj = list.get(0);
			if (obj instanceof Long) return ((Long) obj);
			return 1;
		}
		else if (list.size() > 1) return list.size();
		return 0;
	}
	
	/**
	 * 按hql语句查询
	 * 
	 * @param hql
	 *            hql语句
	 * @param values
	 *            hql语句中的条件（多个）
	 * @return 表的记录
	 */
	protected List<?> queryList(String hql, Map<String, Object> values)
	{
		return getHibernateTemplate().find(hql, values, null);
	}

	/**
	 * 查询hql的分页
	 * @param hql
	 * @param values
	 * @param countHql
	 * @param pageNumber
	 * @param pageSize
	 * @return
	 */
	@SuppressWarnings("rawtypes")
	@Override
	public Page pageQuery(Map<String, Object> paramsMap, int pageNumber, int pageSize) {
		Assert.notNull(paramsMap, "paramsMap can not be null.");
		StringBuilder hql = new StringBuilder();
		StringBuilder countHql = new StringBuilder();
		List<String> valList = new ArrayList<String>();
		countHql.append("select count(*) ");
		hql.append("from ");
		hql.append(((Class) paramsMap.get(QuerySupport.QUERYCLASS)).getName());
		this.hasWhere = false;
		for (Entry<String, Object> en : paramsMap.entrySet()) {
			if (en.getKey().equals(QuerySupport.QUERYCLASS) || en.getKey().equals(QuerySupport.BLOCK) 
					||en.getKey().equals(QuerySupport.ORDERBY)||en.getKey().equals(QuerySupport.ORDERBYDESC)) {
				continue;
			}
			addConditionToQuery(hql, en.getKey(), en.getValue(), valList, hasWhere);
		}
		if (paramsMap.get(QuerySupport.BLOCK) != null && !paramsMap.get(QuerySupport.BLOCK).equals("")) {
			if (this.hasWhere) {
				hql.append(" and ");
				hql.append(paramsMap.get(QuerySupport.BLOCK));

			} else {
				hql.append(" where ");
				hql.append(paramsMap.get(QuerySupport.BLOCK));
			}
		}
		if(paramsMap.get(QuerySupport.ORDERBY) != null){
			hql.append(" order by ");
			hql.append(paramsMap.get(QuerySupport.ORDERBY));
		}
		
		if(paramsMap.get(QuerySupport.ORDERBYDESC) != null){
			hql.append(" order by ");
			hql.append(paramsMap.get(QuerySupport.ORDERBYDESC));
			hql.append(" desc ");
		}
		countHql.append(hql);
		long totalCount = querySize(countHql.toString(), valList.toArray());
		return pageQuery(totalCount, hql.toString(), valList.toArray(), pageNumber, pageSize);
	}

	private StringBuilder addConditionToQuery(StringBuilder sb, String key, Object value, List<String> valList, Boolean hasWhere) {
		if (!hasWhere) {
			sb.append(" where ");
			this.hasWhere = true;
		} else {
			sb.append(" and ");
		}

		if (key.indexOf("|") != -1) {
			String[] keys = key.split("\\|");
			for (int i = 0; i < keys.length; i++) {
				if (i != 0) {
					sb.append(" or ");
				}
				if (keys[i].indexOf("_") != -1) {
					sb.append(keys[i].replaceAll("_", " "));
					if (value != null) {
						sb.append("?");
						valList.add((String) value);
					} else {
						sb.append("");
					}
				} else {
					sb.append(keys[i]);
					sb.append(" = ?");
					valList.add((String) value);
				}
			}
		} else if (key.indexOf("_") != -1) {
			sb.append(key.replaceAll("_", " "));
			if (value != null) {
				sb.append("?");
				valList.add((String) value);
			} else {
				sb.append("");
			}
		} else {
			sb.append(key);
			sb.append(" = ?");
			valList.add((String) value);
		}
		return sb;
	}

	/**
	 * 单表分页
	 * @param pageNumber
	 * @param pageSize
	 * @param values
	 * @return
	 */
	@Override
	public Page getPageListByT(int pageNumber, int pageSize, Object[] values) {
		Page page = new Page();
		if (entityClass == null)
			return page;
		long totalCount = querySize(entityClass);
		long count = Page.getPageCount(totalCount, pageSize);
		pageNumber = (int) (pageNumber > count ? count : pageNumber);
		// ---------------------------------------
		String hql = "from " + entityClass.getName();
		List<?> result = pageList(hql, values, pageNumber, pageSize);
		page.setTotalCount(totalCount);
		page.setResult(result);
		page.setCurrentPage(pageNumber);
		page.setPageSize(pageSize);
		page.setPageCount(count);
		return page;
	}

	protected long querySize(Class<T> clazz) {
		String hql = "select count(*) from " + clazz.getName();
		return querySize(hql, new Object[]{});
	}

	public Page pageQuery(Long totalCount, String hql, Object[] values, int pageNumber, int pageSize) {
		Page pagination = new Page();
		if (totalCount == null)
			totalCount = querySize(hql, values);
		long count = Page.getPageCount(totalCount, pageSize);
		pageNumber = (int) (pageNumber > count ? count : pageNumber);
		List<?> result = pageList(hql, values, pageNumber, pageSize);
		pagination.setTotalCount(totalCount);
		pagination.setResult(result);
		pagination.setCurrentPage(pageNumber);
		pagination.setPageSize(pageSize);
		pagination.setPageCount(count);
		return pagination;
	}

	public List<?> pageList(final String hql, final Object[] values, int pageNumber, final int pageSize) {
		return pageList(hql, values, null, pageNumber, pageSize);
	}

	protected List<?> pageList(final String hql, final Object[] values, final Boolean enableCache, int pageNumber, final int pageSize) {
		return getHibernateTemplate().pageList(hql, values, enableCache, pageNumber, pageSize);
	}

	public List<T> queryById(Serializable id) {
		List<T> rs = new ArrayList<T>();
		T record = findById(id);
		if (record != null)
			rs.add(record);
		return rs;
	}

	public T findById(Serializable id) {
		return (id == null) ? null : getHibernateTemplate().get(entityClass, id);
	}

	/**
	 * 
	 * @param hql
	 * @param values
	 * @return
	 */
	public long querySize(String hql, Object[] values) {
		if (hql == null)
			return 0;
		List<?> list = queryList(hql, values);
		if (list.size() == 1) {
			Object obj = list.get(0);
			if (obj instanceof Long)
				return ((Long) obj);
			return 1;
		} else if (list.size() > 1)
			return list.size();
		return 0;
	}

	public List<?> queryList(String hql, Object[] values) {
		return getHibernateTemplate().find(hql, values);
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public MyHibernateTemplate getHibernateTemplate() {
		return hibernateTemplate;
	}

	@Autowired
	public void setHibernateTemplate(MyHibernateTemplate hibernateTemplate) {
		this.hibernateTemplate = hibernateTemplate;
	}

}
